/**
 * File: XmlControllerFactory.java
 * Created by: mhaimel
 * Created on: 2 Jul 2009
 * CVS:  $Id: XmlControllerFactory.java,v 1.2 2009/10/09 15:00:52 mhaimel Exp $
 */
package uk.ac.ebi.curtain.management.impl;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import uk.ac.ebi.curtain.management.ControllerInfo;
import uk.ac.ebi.curtain.management.TypeInfo;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.io.impl.ClasspathIO;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.VelvetUncheckedException;

import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * @author mhaimel
 *
 */
public class XmlControllerFactory extends AbstractControllerFactory {

	private static final String STEP_IDENTIFIER = "step";
	private static final String QUEUE_IDENTIFIER = "queue";
	private static final String NAME_IDENTIFIER = "name";
	private static final String DESCR_IDENTIFIER = "descr";
	private static final String MAIN_IDENTIFIER = "main";
	private static final String CLASS_IDENTIFIER = "className";
	private static final String PARALLEL_IDENTIFIER = "parallel";
	private static final String SEQUENCE_IDENTIFIER = "sequence";

	public static final String BASE_PATH = "/curtain/*";
	public static final String STEP_PATH = "./step";
	
	private Map<String,List<String>> type2nameList = new HashMap<String, List<String>>();
	private Map<String, SortedMap<Integer, Collection<ControllerInfo>>> type2ControllerMap = new HashMap<String, SortedMap<Integer,Collection<ControllerInfo>>>();
	private List<TypeInfo> typeList = new ArrayList<TypeInfo>();
	private final String nodePath;
	private boolean loaded = false;
	private List<String> pathResources = new ArrayList<String>();
	
	public XmlControllerFactory() {
		this(BASE_PATH);
	}	
	
	public XmlControllerFactory(String nodePath) {
		this.nodePath = nodePath;
	}
	
	public static XmlControllerFactory loadFromFile(File ... fileArr){
		XmlControllerFactory factory = new XmlControllerFactory();
		for(File file : fileArr){
			factory.load(file);
		}
		return factory;
	}
	
	public void load(File file){
		BufferedInputStream in = null;
		try {
			in = new FileIO(file).getBufferedInputStream();
			load(in);
		} catch (IOException e) {
			throw new VelvetUncheckedException("Problems reading file " + file,e);
		} finally{
			FileIO.closeQuietly(in);
		}
	}
	
	private String getNodePath() {
		return nodePath;
	}
	

	public void load(BufferedInputStream in) throws CurtainUncheckedException {
		InputSource insource = new InputSource(in);
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		NodeList nodeList = evaluate(
					getNodePath(),
					getRootNode(insource), 
					xpath);
		for(int i = 0; i < nodeList.getLength(); ++i ){
			Node cNode = nodeList.item(i);
			if(!cNode.getNodeName().equalsIgnoreCase(QUEUE_IDENTIFIER)){
				throw new IllegalArgumentException("Node " + QUEUE_IDENTIFIER + " expected");
			}
			String contrName = getAttribute(cNode,NAME_IDENTIFIER); 
			String descr = getAttributeIfAvailable(cNode, DESCR_IDENTIFIER, "");
			Boolean isMain = Boolean.valueOf(getAttributeIfAvailable(cNode, MAIN_IDENTIFIER, Boolean.FALSE.toString()));
			TypeInfo tInfo = new TypeInfo(contrName, descr, isMain);
			addType(tInfo);
			NodeList qNodeList = evaluate(STEP_PATH, cNode, xpath);
			for(int j = 0; j < qNodeList.getLength(); ++j){
				Node stepNode = qNodeList.item(j);
				if(!stepNode.getNodeName().equalsIgnoreCase(STEP_IDENTIFIER)){
					throw new IllegalArgumentException("Node " + STEP_IDENTIFIER + " expected");
				}
				String nameNode = getAttribute(stepNode,NAME_IDENTIFIER);
				String classNode = getAttribute(stepNode,CLASS_IDENTIFIER); 
				String parallelNode = getAttribute(stepNode,PARALLEL_IDENTIFIER);
				String sequNode = getAttribute(stepNode,SEQUENCE_IDENTIFIER);
				Integer parallelNumber = Integer.valueOf(parallelNode);
				Integer sequNumber = Integer.valueOf(sequNode);
				ControllerInfo info = new ControllerInfo(nameNode,parallelNumber,classNode,sequNumber, contrName);
				addController(info);
			}
		}
	}
	
	private void addType(TypeInfo tInfo) {
		typeList.add(tInfo);
	}

	private String getAttribute(Node node, String name){
		Attr attr = (Attr) node.getAttributes().getNamedItem(name);
		if(null == attr ){
			throw new IllegalArgumentException("Attribute '" + name + "' expected in node: " + node);
		}
		return attr.getValue();
	}
	private String getAttributeIfAvailable(Node node, String name, String defVal){
		Attr attr = (Attr) node.getAttributes().getNamedItem(name);
		if(null == attr ){
			return defVal;
		}
		return attr.getValue();
	}

	private NodeList evaluate(String path, Node node, XPath xpath){
		try {
			return (NodeList) xpath.evaluate(
					path,
					node, 
					XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			throw new CurtainUncheckedException("Problems parsing Path " + path,e);
		}
	}
	
	private void addController(ControllerInfo info){
//		if(contrNameMap.containsKey(info.getName())){
//			throw new CurtainUncheckedException("Controllers with same name specified: " + info.getName());
//		}
		SortedMap<Integer, Collection<ControllerInfo>> cMap = type2ControllerMap.get(info.getType());
		if(null == cMap){
			cMap = new TreeMap<Integer, Collection<ControllerInfo>>();
			type2ControllerMap.put(info.getType(), cMap);
		}
		Collection<ControllerInfo> contrColl = cMap.get(info.getSequence());
		if(null == contrColl){
			contrColl = new HashSet<ControllerInfo>();
			cMap.put(info.getSequence(), contrColl);
		}
		contrColl.add(info);
//		contrNameMap.put(info.getName(), info);
		List<String> nList = type2nameList.get(info.getType());
		if(null == nList){
			nList = new ArrayList<String>();
			type2nameList.put(info.getType(), nList);
		}
		nList.add(info.getName());
	}
	
	/**
	 * Retrieve the root node for the source
	 * 
	 * @return
	 */
	private Node getRootNode(InputSource insource) {
		try {
			DocumentBuilder builder = DocumentBuilderFactory
					.newInstance().newDocumentBuilder();
			return builder.parse(insource);
		} catch (ParserConfigurationException e) {
			throw new CurtainUncheckedException(
					"Could not get XML parser configuration", e);
		} catch (SAXException e) {
			throw new CurtainUncheckedException("Could not parse XML",
					e);
		} catch (IOException e) {
			throw new CurtainUncheckedException(
					"Could not read in XML", e);
		}
	}
	
	private boolean isLoaded() {
		return loaded;
	}

//	/**
//	 * {@inheritDoc}
//	 */
//	@Override
//	public ControllerInfo getController(String name) {
//		if(!isLoaded()){
//			loadAll();
//		}
//		return contrNameMap.get(name);
//	}

	private void loadAll() {
		for(String res : getPathResources()){
			load(res);
		}		
		this.loaded = true;
	}
	
	public void load(String classPath){
		BufferedInputStream in = null;
		try {
			in = new ClasspathIO(classPath).getBufferedInputStream();
			load(in);
		} catch (IOException e) {
			throw new VelvetUncheckedException("Problems reading file from classpath " + classPath,e);
		} finally{
			FileIO.closeQuietly(in);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public SortedMap<Integer, Collection<ControllerInfo>> getControllerMap(String type) {
		if(!isLoaded()){
			loadAll();
		}
		return type2ControllerMap.get(type); 
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<String> getNames(String type) {
		if(!isLoaded()){
			loadAll();
		}
		return type2nameList.get(type);
	}
	
	private List<String> getPathResources() {
		return pathResources;
	}
	
	@Inject
	public void addPathResource(@Named("pipeline-config") String resource){
		getPathResources().add(resource);
	}

	@Override
	public List<TypeInfo> getTypes() {
		if(!isLoaded()){
			loadAll();
		}
		return Collections.unmodifiableList(this.typeList);
	}
}
